package com.lm.integration;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.LockedAccountException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.config.Ini;
import org.apache.shiro.config.IniSecurityManagerFactory;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.util.Factory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author liming356
 * @since 2018/11/7/007
 */
public class ShiroStart {

  private static final transient Logger log = LoggerFactory.getLogger(ShiroStart.class);

  public static void main(String[] args) {
    log.info("My First Apache Shiro Application");

    //1.使用 Shiro 的 IniSecurityManagerFactory 加载shiro.ini 文件
    Factory<SecurityManager> factory = new IniSecurityManagerFactory("classpath:config/shiro.ini");

    //2.分析 INI 文件并根据配置文件返回一个 SecurityManager 实例
    SecurityManager securityManager = factory.getInstance();

    //3.将 SecurityManager 设置成了static (memory) singleton，可以通过 JVM 访问
    //在其它成熟的应用环境中，通常会将 SecurityManager 放在程序指定的存储中（如在 web 中的 ServletContexct 或者 Spring、Guice、 JBoss DI 容器实例）中
    SecurityUtils.setSecurityManager(securityManager);

    //Shiro 的 API 提供了'the current user'概念，即 Subject。

    Subject currentUser = SecurityUtils.getSubject();

    //Session 是 shiro 指定的一个实例，提供基本上所有 HttpSession 的功能，但具备额外的好处和不同：它不需要一个 HTTP 环境！
    Session session = currentUser.getSession();
    session.setAttribute( "someKey", "aValue" );

    if (!currentUser.isAuthenticated()) {
      //收集用户的主要信息和凭据，来自GUI中的特定的方式
      //如包含用户名/密码的HTML表格，X509证书，OpenID，等。
      //我们将使用用户名/密码的例子因为它是最常见的。.
      UsernamePasswordToken token = new UsernamePasswordToken("lonestarr1", "vespa");

      //支持'remember me' (无需配置，内建的!):
      token.setRememberMe(true);

      try {
        currentUser.login(token);
        // 无异常，说明就是我们想要的!
      } catch (UnknownAccountException uae) {
        log.error("username 不存在，给个错误提示?" + token.getPrincipal());
        // username 不存在，给个错误提示?
      } catch (IncorrectCredentialsException ice) {
        log.error("password 不匹配，再输入?" + token.getPrincipal());
        // password 不匹配，再输入?
      } catch (LockedAccountException lae) {
        log.error("账号锁住了，不能登入。给个提示?" + token.getPrincipal());
        // 账号锁住了，不能登入。给个提示?
      } catch (AuthenticationException e) {
        log.error("未考虑到的问题 - 错误?" + token.getPrincipal());
        // 未考虑到的问题 - 错误?
      }

      //打印主要识别信息 (本例是 username):
      log.info( "用户 [" + currentUser.getPrincipal() + "] 登录成功" );

      // 判断他们是否拥有某个角色
      if (currentUser.hasRole("schwartz")) {
        log.info("May the Schwartz be with you!");
      } else {
        log.info("Hello, mere mortal.");
      }

      //判断他们是否拥有某个特定动作或入口的权限
      if ( currentUser.isPermitted( "lightsaber:weild" ) ) {
        log.info("You may use a lightsaber ring.  Use it wisely.");
      } else {
        log.info("Sorry, lightsaber rings are for schwartz masters only.");
      }

      // 还可以执行非常强大的 instance-level （实例级别）的权限检测，检测用户是否具备访问某个类型特定实例的权限
      if ( currentUser.isPermitted( "winnebago:drive:eagle5" ) ) {
        log.info("You are permitted to 'drive' the 'winnebago' with license plate (id) 'eagle5'. Here are the keys - have fun!");
      } else {
        log.info("Sorry, you aren't allowed to drive the 'eagle5' winnebago!");
      }
    }

    currentUser.logout(); //清楚识别信息，设置 session 失效.

    // 通过INI实例创建SecurityManager
    Ini ini = new Ini();
    ini.setSectionProperty("section", "name", "value");
    Factory<SecurityManager> factory1 = new IniSecurityManagerFactory(ini);

    /**
     * 键名在每个区域内必须唯一，但在整个配置文件中并不需要这样 每一个区域（section）可以看作是一个独立的Properties 定义
     *
     * [main]区域是配置程序 SecurityManager 实例及其支撑组件的地方，如 Realm。 通过INI配置像 SecurityManager
     * 的对象实例及其支撑组件听起来是一件很困难的事情，因为在这里我们只能用key/value对。但通过定义一些对象视图（graphs）可以理解的惯例，你发现你完全可以这样做。Shiro
     * 利用这些假定的惯例来实现一个简单而简明的配置途径。
     *
     * 一、[main]区域
     * [main]
     * sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
     *
     * 实例化：这一行实例化了一个类型为 DatabaseRealm 的对象实例并且,使用 myRealm 作为对象名称以便于将来引用和配置。
     * myRealm = com.company.security.shiro.DatabaseRealm
     *
     * 设置对象属性：等同于 myRealm.setConnectionTimeout(30000)
     * myRealm.connectionTimeout = 30000
     * myRealm.username = jsmith
     * myRealm.password = secret
     *
     * 引用值：$ 符来引用一个之前定义的实例
     * myRealm.credentialsMatcher = $sha256Matcher
     *
     * 嵌套属性：等同于 securityManager.getSessionManager().setGlobalSessionTimeout(1800000)
     * securityManager.sessionManager.globalSessionTimeout = 1800000
     *
     * 字节数组值：因为原始的字节数组不能直接在文本中定义，我们必须使用字节数组的文本编码。可以使用64位编码（默认）或者16位编码，默认为64位编码因为使用64位编码实际文字会少
     * securityManager.rememberMeManager.cipherKey = kPH+bIxk5D2deZiIxcaaaA==
     * 如果你想使用16位编码，你必须在字串前面加上 0x 前缀：
     * securityManager.rememberMeManager.cipherKey = 0x3707344A4093822299F31D008
     *
     * 集合属性 列表（Lists）、集合（Sets）、图（Maps）可以像其它属性一样设置--直接设置或者像嵌套属性一样，对于列表和集合，只需指定一个逗号分割的值集或者对象引用集。
     *
     * 如定义一些SessionListeners：
     * sessionListener1 = com.company.my.SessionListenerImplementation
     * sessionListener2 = com.company.my.other.SessionListenerImplementation
     * securityManager.sessionManager.sessionListeners = $sessionListener1, $sessionListener2
     *
     * object1 = com.company.some.Class
     * object2 = com.company.another.Class
     * anObject = some.class.with.a.Map.property anObject.mapProperty = key1:$object1, key2:$object2
     *
     * 注意事项：
     * 1.顺序问题：每一个对象实例以及每一个指定的值都将按照其在 [main] 区域中产生的顺序的执行
     * 2.覆盖实例：每一个对象都可以被后定义的新实例覆盖
     * 3.默认Default
     * SecurityManager：securityManager实例是特殊的--它已经为你实例化过了并且准备好了，所以你并不需要知道指定的实例化SecurityManager的实现类
     *
     *
     *
     * 二、[users]区域：
     * 允许你定义一组静态的用户帐号，这对于那些只有少数用户帐号并且用户帐号不需要在运行时动态创建的环境来说非常有用。下面是一个例子：
     *
     * [users]
     * admin = secret
     * lonestarr = vespa, goodguy, schwartz
     * darkhelmet = ludicrousspeed, badguy, schwartz
     *
     * 定义非空的[users]或[roles]区域将自动创建org.apache.shiro.realm.text.IniRealm 实例,在[main]区域下生成一个可用的iniRealm ，你可以像上面配置其它对象那样配置它。
     *
     * Line Format 格式 [users]区域下每一行必须和下面的形式一致： username = password, roleName1, roleName2, ...,
     * roleNameN 等号左边的值是用户名； 等号右侧第一个值是用户密码，密码是必须的； 密码之后用逗号分割的值是赋予用户的角色名，角色名是可选的。
     *
     * 如果你不希望[users]区域下的密码以明文显示，你可以用你喜欢的哈希算法（MD5, Sha1, Sha256, 等）来加密它们，将加密后的字符串作为密码值。
     * 默认的，密码建议用16位编码算法，但也可以用64位编码算法替代一旦你指定了加密后的密码值，你必须告诉 shiro 它们是加密的，
     * 你可以通过配置配置在[main]隐含创建的iniRealm相应的CredentialsMatcher 实现来告知你使用的哈希算法：
     *
     * sha256Matcher = org.apache.shiro.authc.credential.Sha256CredentialsMatcher
     * iniRealm.credentialsMatcher = $sha256Matcher
     *
     * 如果你用64位编码方式取代了16位编码方式，你应该指定：
     * # true = hex, false = base64:
     * sha256Matcher.storedCredentialsHexEncoded = false
     *
     *
     * 三、[roles]
     * [roles]区域允许你将权限和在[users]定义的角色对应起来
     *
     * 注意如果一个特定的权限定义需要用到逗号分隔（如：printer:5thFloor:print,info），你需要将该定义用双引号括起来从而避免出错："printer:5thFloor:print,info"
     * [roles]区域下的每一行必须用下面的格式定义角色-权限的键/值对应关系。
     * rolename = permissionDefinition1, permissionDefinition2, ..., permissionDefinitionN
     *
     * 四、[urls]
     *
     *
     * Authentication 认证
     *
     * Principals(身份) 是Subject的“标识属性”，可以是任何与Subject相关的标识，比如说名称（给定名称）、名字（姓或者昵称）、用户名、安全号码等等。
     * 当然像昵称这样的内容不能很好的对Subject进行独特标识，所以最好的身份信息（Principals）是使用在程序中唯一的标识--典型的使用用户名或邮件地址。
     *
     * Primary Principal(最主要的身份)虽然 Shiro 可以使用任何数量的身份，Shiro 还是希望一个程序精确地使用一个主要的身份--一个仅有的唯一标识 Subject 值。
     * 在多数程序中经常会是一个用户名、邮件地址或者全局唯一的用户 ID。
     *
     * Credentials(证明) 通常是只有 Subject 知道的机密内容，用来证明他们真正拥有所需的身份，一些简单的证书例子如密码、指纹、眼底扫描和X.509证书等。
     *
     * 最常见的身份证明是用户名和密码，用户名是所需的身份说明，密码是证明身份的证据。如果一个提交的密码和系统要求的一致，程序就认为该用户身份正确，因为其他人不应该知道同样的密码。
     *
     *
     * Subject 验证的过程可以有效地划分分以下三个步骤：
     * 1.收集 Subject 提交的身份和证明；
     *
     * 最常用的情况是 username/password 对:
     * UsernamePasswordToken token = new UsernamePasswordToken(username, password)
     * ”Remember Me” 功能是内建的
     * token.setRememberMe(true)
     * 从程序终端用户获取信息的过程与 Shiro 的 AuthenticationToken 完全无关。
     *
     * 2.向 Authentication 提交身份和证明；
     * 3.如果提交的内容正确，允许访问，否则重新尝试验证或阻止访问
     *
     *
     *
     *
     *
     *
     *
     */












    System.exit(0);
  }

}
